<template>
  <div
    class="node"
    :class="{
      'has-children': hasChildren,
      'is-dragged': isDragged
    }"
    draggable="true"
    @dragstart.stop="onDragStart"
    @dragleave.stop="onDragLeave"
    @dragend.stop="onDragEnd"
    @drop.stop="onDrop"
    @dragover.prevent
    @contextmenu.stop="onClickContextMenu"
  >
    <div
      :id="node.id"
      ref="rowRef"
      class="node__row"
      :class="{
        'is-hovered': (isHovered && isAllowed) || hoveredNodeId === node.id,
        'is-selected': isSelected,
        'is-focused': isFocused,
        'is-highlighted': isHighlighted
      }"
      @dragenter.stop="onDragEnter"
      @dragover="onDragOver"
      @click="onClickNode(node.id)"
    >
      <span class="node__name">
        <UniconsAngleRight
          v-if="node.children.length"
          class="node__arrow"
          :class="{ 'node__arrow--down': node.isOpen }"
          @click.stop="onClickArrow(node.id)"
        />
        <span
          v-else
          class="node__arrow-placeholder"
        />
        <slot
          :node="node"
          :deep="deep"
        >
          {{ node.name }}
        </slot>
      </span>
    </div>
    <template v-if="node.children">
      <AppTreeNode
        v-for="(children, idx) in node.children"
        v-show="node.isOpen"
        :key="children.id"
        class="children"
        :index="idx"
        :deep="deep + 1"
        :node="children"
        :nodes="node.children"
        :hovered-node-id="hoveredNodeId"
      >
        <template #default="scoped: any">
          <slot
            :node="scoped.node"
            :deep="scoped.deep"
            :hovered-node-id="hoveredNodeId"
          />
        </template>
      </AppTreeNode>
    </template>
    <svg
      v-if="isShowBetweenLine"
      height="10"
      :style="betweenLineStyle"
    >
      <circle
        cx="5"
        cy="5"
        r="3"
        stroke="var(--color-primary)"
        fill="none"
        stroke-width="2"
      />
      <line
        x1="100%"
        x2="8"
        y1="5"
        y2="5"
        stroke="var(--color-primary)"
        stroke-width="2"
      />
    </svg>
  </div>
</template>

<script setup lang="ts">
import { onClickOutside } from '@vueuse/core'
import type { CSSProperties, Ref } from 'vue'
import { computed, inject, ref } from 'vue'
import {
  store,
  isAllowed,
  insertNodeByPosition,
  pushNode,
  toggleNode
} from './composable'
import type { Node, Position } from './types'

interface Props {
  index: number
  deep?: number
  node: Node
  nodes: Node[]
  indent?: number
  hoveredNodeId?: string
  ghostEl?: Function
}

const props = withDefaults(defineProps<Props>(), {
  index: 0,
  deep: 0,
  indent: 10,
  deepArray: () => [0]
})

const updateValue = inject('updateValue') as Function
const clickNode = inject('clickNode') as Function
const contextMenuHandler = inject('contextMenuHandler') as Function
const isHoveredByIdDisabled = inject('isHoveredByIdDisabled') as Ref
const focusHandler = inject('focusHandler') as Function

const hoveredId = ref()
const overPosition = ref<Position>()
const isDragged = ref(false)
const isFocused = ref(false)
const isHighlighted = ref(false)
const rowRef = ref<HTMLElement>()

// COMPUTED
const hasChildren = computed(() => props.node.children.length > 0)

const isFirst = computed(() => props.index === 0)

const isHovered = computed(() => {
  return props.node.id === hoveredId.value && overPosition.value === 'center'
})

const isSelected = computed(() => store.selectedId === props.node.id)

const isShowBetweenLine = computed(() => {
  if (!store.dragNode) return false
  return overPosition.value === 'before' || overPosition.value === 'after'
})

const indentStyle = computed(() => {
  return props.indent + 'px'
})

const hoveredOffsetStyle = computed(() => {
  return -(props.deep * props.indent) + 'px'
})

const betweenLineStyle = computed(() => {
  const style: CSSProperties = {
    position: 'absolute',
    width: `calc(100% - ${props.deep} * ${indentStyle.value})`
  }

  if (overPosition.value === 'before') {
    style.top = '-6px'
  }

  if (overPosition.value === 'after') {
    style.bottom = '-6px'
  }

  return style
})

// METHODS
const onClickArrow = (id: string) => {
  toggleNode(id)
  updateValue()
}

const onClickNode = (id: string) => {
  store.selectedId = id
  isFocused.value = true
  clickNode(id)
}

const onClickContextMenu = async () => {
  isHighlighted.value = true
  isHighlighted.value = await contextMenuHandler()
}

const onDragStart = (e: DragEvent) => {
  store.dragNode = props.node
  isHoveredByIdDisabled.value = true

  const el = document.createElement('div')
  const style = {
    position: 'fixed',
    left: '-100%',
    color: 'var(--color-text)',
    fontSize: 'var(--text-md)'
  }

  el.id = 'ghost'
  el.innerHTML = props.node.name

  Object.assign(el.style, style)
  document.body.appendChild(el)

  e.dataTransfer!.setDragImage(el, 0, 0)
  setTimeout(() => el.remove(), 0)

  e.dataTransfer!.setData('node', JSON.stringify(props.node))
}

const onDragEnd = () => {
  store.dragNode = undefined
  store.dragEnterNode = undefined
  overPosition.value = undefined
  isDragged.value = false
  isHoveredByIdDisabled.value = false
}

const onDragEnter = () => {
  hoveredId.value = props.node.id
  store.dragEnterNode = props.node
}

const onDragOver = (e: DragEvent) => {
  hoveredId.value = props.node.id

  if (props.node?.id === store.dragNode?.id) return

  const height = rowRef.value!.offsetHeight
  const before = height * 0.3
  const after = height - before

  if (e.offsetY < before && isFirst.value) {
    overPosition.value = 'before'
  } else if (e.offsetY > after) {
    overPosition.value = 'after'
  } else {
    overPosition.value = 'center'
  }
}

const onDragLeave = (e: DragEvent) => {
  (e.target as HTMLElement).style.background = ''
  hoveredId.value = undefined
  overPosition.value = undefined
}

const onDrop = () => {
  if (overPosition.value === 'after') {
    insertNodeByPosition('after', props.node, store.dragNode!)
    updateValue()
  }

  if (overPosition.value === 'before') {
    insertNodeByPosition('before', props.node, store.dragNode!)
    updateValue()
  }

  if (overPosition.value === 'center') {
    pushNode(props.node, store.dragNode!)
    updateValue()
  }

  overPosition.value = undefined
}

onClickOutside(rowRef, () => {
  isFocused.value = false
  isHighlighted.value = false
})

if (focusHandler) focusHandler(isFocused)
</script>

<style lang="scss" scoped>
$color-blue: #0063e1;
$color-grey: #8c8c8c;
.node {
  $r: &;
  position: relative;
  user-select: none;
  + .node {
    margin-top: 2px;
  }
  &__row {
    user-select: none;
    position: relative;
    display: flex;
    &.is-hovered,
    &.is-selected,
    &.is-focused,
    &.is-highlighted {
      color: #fff;
      position: relative;
      #{$r}__arrow {
        fill: #fff;
      }
      &::before {
        content: '';
        left: v-bind(hoveredOffsetStyle);
        right: 0;
        top: 0;
        bottom: 0;
        position: absolute;
        background-color: var(--color-primary);
        z-index: 0;
        border-radius: 5px;
      }
    }
    &.is-selected {
      #{$r}__arrow {
        fill: var(--color-text);
      }
      color: var(--color-text);
      &::before {
        background-color: var(--color-sidebar-item-selected);
      }
      :deep(svg) {
        fill: var(--color-text);
      }
    }
    &.is-focused,
    &.is-hovered {
      color: #fff;
      #{$r}__arrow {
        fill: #fff;
      }
      :deep(svg) {
        fill: #fff !important;
      }
      &::before {
        background-color: var(--color-primary);
      }
    }
    &.is-highlighted {
      color: var(--color-text);
      &::before {
        background-color: transparent;
        border: 2px solid var(--color-primary);
      }
      :deep(svg) {
        fill: var(--color-text) !important;
      }
      &.is-focused {
        :deep(svg) {
          fill: var(--color-text);
        }
      }
      &.is-selected {
        :deep(svg) {
          fill: var(--color-text) !important;
        }
      }
    }
  }
  &__name {
    display: flex;
    align-items: center;
    position: relative;
    z-index: 1;
    width: 100%;
  }
  .children {
    padding-left: 10px;
    padding-left: v-bind(indentStyle);
  }
  &__arrow {
    position: relative;
    color: $color-grey;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 19px;
    height: 19px;
    fill: $color-grey;
    z-index: 1;
    &-placeholder {
      width: 19px;
    }
    &--down {
      transform: rotate(90deg);
    }
  }
}
</style>
